package org.example.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import lombok.extern.slf4j.Slf4j;
import org.example.domain.activity.event.ActivitySkuStockZeroMessageEvent;
import org.example.domain.activity.model.aggregate.CreateOrderAggregate;
import org.example.domain.activity.model.entity.*;
import org.example.domain.activity.model.valobj.ActivityStateVO;
import org.example.domain.activity.model.valobj.OrderStateVO;
import org.example.domain.activity.model.valobj.SubtractionSkuStockMessageVO;
import org.example.domain.activity.model.valobj.UserRaffleOrderStateVO;
import org.example.domain.activity.repository.IActivityRepository;
import org.example.infrastructure.event.EventPublisher;
import org.example.infrastructure.persistent.dao.*;
import org.example.infrastructure.persistent.po.*;
import org.example.infrastructure.persistent.redis.IRedisService;
import org.example.types.common.Constants;
import org.example.types.enums.ResponseCode;
import org.example.types.exception.AppException;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Repository
public class ActivityRepository implements IActivityRepository {


    @Autowired
    private IRaffleActivitySkuDao activitySkuDao;

    @Autowired
    private IRaffleActivityCountDao activityCountDao;

    @Autowired
    private IRaffleActivityDao activityDao;

    @Autowired
    private IRaffleActivityOrderDao activityOrderDao;

    @Autowired
    private IRaffleActivityAccountDao activityAccountDao;

    @Autowired
    private IRaffleActivityAccountDayDao activityAccountDayDao;

    @Autowired
    private IRaffleActivityAccountMonthDao activityAccountMonthDao;

    @Autowired
    private IUserRaffleOrderDao userRaffleOrderDao;

    @Resource
    private IDBRouterStrategy dbRouter;

    @Resource
    private TransactionTemplate transactionTemplate;


    @Resource
    private IRedisService redisService;

    @Resource
    private EventPublisher eventPublisher;

    @Resource
    private ActivitySkuStockZeroMessageEvent activitySkuStockZeroMessageEvent;

    private final SimpleDateFormat dateFormatMonth = new SimpleDateFormat("yyyy-MM");
    private final SimpleDateFormat dateFormatDay = new SimpleDateFormat("yyyy-MM-dd");


    @Override
    public ActivityCountEntity queryActivityCountEntityById(Long raffleActivityCountId) {
        RaffleActivityCount raffleActivityCount = activityCountDao.queryRaffleActivityCountById(raffleActivityCountId);
        ActivityCountEntity activityCountEntity = ActivityCountEntity.builder()
                .activityCountId(raffleActivityCount.getActivityCountId())
                .totalCount(raffleActivityCount.getTotalCount())
                .dayCount(raffleActivityCount.getDayCount())
                .monthCount(raffleActivityCount.getMonthCount())
                .build();
        return activityCountEntity;
    }

    @Override
    public ActivityEntity queryActivityEntityByActivityId(Long activityId) {
        RaffleActivity raffleActivity= activityDao.queryRaffleActivityByActivityId(activityId);

        ActivityEntity activityEntity = ActivityEntity.builder()
                .activityId(raffleActivity.getActivityId())
                .activityName(raffleActivity.getActivityName())
                .activityDesc(raffleActivity.getActivityDesc())
                .beginDateTime(raffleActivity.getBeginDateTime())
                .endDateTime(raffleActivity.getEndDateTime())
                .stockCount(raffleActivity.getStockCount())
                .stockCountSurplus(raffleActivity.getStockCountSurplus())
                .strategyId(raffleActivity.getStrategyId())
                .state(ActivityStateVO.valueOf(raffleActivity.getState()))
                .build();
        return activityEntity;

    }

    @Override
    public ActivitySkuEntity queryActivitySkuEntityBySku(Long sku) {
        RaffleActivitySku raffleActivitySku = activitySkuDao.queryRaffleActivitySkuBySku(sku);

        ActivitySkuEntity activitySkuEntity = ActivitySkuEntity.builder()
                .sku(raffleActivitySku.getSku())
                .activityId(raffleActivitySku.getActivityId())
                .activityCountId(raffleActivitySku.getActivityCountId())
                .stockCountSurplus(raffleActivitySku.getStockCountSurplus())
                .stockCount(raffleActivitySku.getStockCount())
                .build();
        return activitySkuEntity;
    }

    @Override
    public ActivityAccountEntity queryActivityAccountEntityByUserId(String userId,Long activityId) {
        RaffleActivityAccount raffleActivityAccount = activityAccountDao.queryRaffleActivityAccountByUserId(
                RaffleActivityAccount.builder()
                        .activityId(activityId)
                        .userId(userId)
                        .build()
        );
        ActivityAccountEntity activityAccountEntity = ActivityAccountEntity.builder()
                .userId(raffleActivityAccount.getUserId())
                .activityId(raffleActivityAccount.getActivityId())
                .totalCount(raffleActivityAccount.getTotalCount())
                .totalCountSurplus(raffleActivityAccount.getTotalCountSurplus())
                .dayCount(raffleActivityAccount.getDayCount())
                .dayCountSurplus(raffleActivityAccount.getDayCountSurplus())
                .monthCount(raffleActivityAccount.getMonthCount())
                .monthCountSurplus(raffleActivityAccount.getMonthCountSurplus())
                .build();

        return activityAccountEntity;
    }



    @Override
    public List<ActivityOrderEntity> queryActivityOrderEntityListByUserId(String userId) {

        List<ActivityOrderEntity> activityOrderEntityList = new ArrayList<>();
        List<RaffleActivityOrder> raffleActivityOrderList = activityOrderDao.queryRaffleActivityOrderByUserId(userId);

        for (RaffleActivityOrder raffleActivityOrder :raffleActivityOrderList){
            ActivityOrderEntity activityOrderEntity = ActivityOrderEntity.builder()
                        .userId(raffleActivityOrder.getUserId())
                        .activityId(raffleActivityOrder.getActivityId())
                        .activityName(raffleActivityOrder.getActivityName())
                        .strategyId(raffleActivityOrder.getStrategyId())
                        .orderId(raffleActivityOrder.getOrderId())
                        .orderTime(raffleActivityOrder.getOrderTime())
                        .state(OrderStateVO.valueOf(raffleActivityOrder.getState()))
                        .totalCount(raffleActivityOrder.getTotalCount())
                        .dayCount(raffleActivityOrder.getDayCount())
                        .monthCount(raffleActivityOrder.getMonthCount())
                        .build();

            activityOrderEntityList.add(activityOrderEntity);
        }

        return activityOrderEntityList;
    }


    /**
     * 将聚合对象保存到数据库
     * 总额度增加 日额度增加 月额度增加
     * 活动sku订单插入
     * @param createOrderAggregate 聚合对象
     */
    @Override
    public void doSaveOrder(CreateOrderAggregate createOrderAggregate) {
        //1.构建订单数据
        ActivityOrderEntity activityOrderEntity =createOrderAggregate.getActivityOrderEntity();
        RaffleActivityOrder raffleActivityOrder = RaffleActivityOrder.builder()
                .userId(activityOrderEntity.getUserId())
                .sku(activityOrderEntity.getSku())
                .activityId(activityOrderEntity.getActivityId())
                .activityName(activityOrderEntity.getActivityName())
                .strategyId(activityOrderEntity.getStrategyId())
                .orderId(activityOrderEntity.getOrderId())
                .orderTime(activityOrderEntity.getOrderTime())
                .state(activityOrderEntity.getState().getCode())
                .totalCount(activityOrderEntity.getTotalCount())
                .dayCount(activityOrderEntity.getDayCount())
                .monthCount(activityOrderEntity.getMonthCount())
                .outBusinessNo(activityOrderEntity.getOutBusinessNo())//用来防止同一次行为多次下单,数据库中这个字段是唯一索引
                .build();


        //2.构建账户数据
        RaffleActivityAccount raffleActivityAccount = RaffleActivityAccount.builder()
                .userId(createOrderAggregate.getUserId())
                .activityId(createOrderAggregate.getActivityId())
                .totalCount(createOrderAggregate.getTotalCount())
                .totalCountSurplus(createOrderAggregate.getTotalCount())
                .dayCount(createOrderAggregate.getDayCount())
                .dayCountSurplus(createOrderAggregate.getDayCount())
                .monthCount(createOrderAggregate.getMonthCount())
                .monthCountSurplus(createOrderAggregate.getMonthCount())
                .build();

        //构建月账户数据
        Date date = new Date();
        String month = dateFormatMonth.format(date);
        String day = dateFormatDay.format(date);

        RaffleActivityAccountMonth raffleActivityAccountMonth = RaffleActivityAccountMonth.builder()
                .activityId(createOrderAggregate.getActivityId())
                .userId(createOrderAggregate.getUserId())
                .monthCountSurplus(createOrderAggregate.getMonthCount())
                .monthCount(createOrderAggregate.getMonthCount())
                .month(month)
                .build();

        RaffleActivityAccountDay raffleActivityAccountDay = RaffleActivityAccountDay.builder()
                .activityId(createOrderAggregate.getActivityId())
                .userId(createOrderAggregate.getUserId())
                .day(day)
                .dayCount(createOrderAggregate.getDayCount())
                .dayCountSurplus(createOrderAggregate.getDayCount())
                .build();

        dbRouter.doRouter(createOrderAggregate.getUserId()); //在这里进行路由,而不是注解路由,这样可以保证同一次连接
        //编程式事务,用来自由灵活处理回滚,不单单只是出错时
        transactionTemplate.execute(status -> {
//            try{
                //3.将两种数据用事务添加进数据库
                activityOrderDao.insert(raffleActivityOrder);
                int count = activityAccountDao.updateAccountQuota(raffleActivityAccount);
                int countDay = activityAccountDayDao.updateActivityAccountDayAddQuota(raffleActivityAccountDay);
                int countMonth = activityAccountMonthDao.updateActivityAccountMonthAddQuota(raffleActivityAccountMonth);
                if (count != 0 || countDay != 0 || countMonth != 0) {
                    throw new AppException("额度更新出错");
                }

                return 1;
//            }catch (DuplicateKeyException e){
//                status.setRollbackOnly();
//                log.info("唯一索引冲突");
//                throw new AppException(ResponseCode.INDEX_DUP.getCode());
//            }finally {
//                dbRouter.clear();
//            }

        });






    }

    @Override
    public void assembleSkuStockInRedis(ActivitySkuEntity activitySkuEntity) {

        Long stock = activitySkuEntity.getStockCountSurplus();
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + activitySkuEntity.getSku();

        redisService.setAtomicLong(cacheKey,stock);
    }

    /**
     * 根据sku在redis中扣减 sku的库存
     * 根据endDateTime设置流水锁过期时间
     * 流水锁: 对于每一个扣减了的库存值加锁,比如库存从99到了98,获取98这个库存数的锁,没有则加锁,有的话就加锁失败,说明库存扣减到98了
     *         为了防止网络抖动,两个相同的请求进来,同时将库存从99扣减到98
     * @param sku
     * @param endDateTime 活动截至时间, 根据这个设置redis里sku库存的流水锁的过期时间,因为活动过期这个锁也就没用了
     * @return redis库存是否扣减成功
     */
    @Override
    public Boolean subtractionSkuStockFromRedis(Long sku, Date endDateTime) {


        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
        Long surplus = redisService.decr(cacheKey);
        if (surplus == 0){
            //库存扣减为0时发送MQ通知mysql直接更新相关库存为0
            //这样的设计是为了减少数据库压力,每扣减一个就通过mysql库存-1的话会很增加数据库压力,直接为0就不用趋势性更新了
            log.info("库存到0 了");
            eventPublisher.publish(activitySkuStockZeroMessageEvent.topic(), activitySkuStockZeroMessageEvent.buildEventMessage(sku));
        } else if (surplus < 0){
            //扣减后库存小于0,扣减失败
            redisService.setValue(cacheKey , 0);
            return false;
        }

        //给库存加流水锁,即扣减的库存都在redis给一个记录,如果系统因为什么恢复库存时就避免了超卖
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Long expireMillis = endDateTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
        Boolean lock=redisService.setNx(lockKey,expireMillis,TimeUnit.MILLISECONDS);
        if (!lock){
            log.info("库存流水锁加锁失败");
            throw new AppException("库存流水锁加锁失败");
        }

        return lock;

    }

    /**
     * 发送sku库存扣减消息用于通知mysql扣减库存
     * 采用redisson的延迟消息发送
     * @param build 待发送消息
     */
    @Override
    public void sendSubtractionSkuStockMessage(SubtractionSkuStockMessageVO build,Long sku) {
        RBlockingQueue<SubtractionSkuStockMessageVO> queue = redisService.getBlockingQueue(Constants.RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY + sku);
        RDelayedQueue<SubtractionSkuStockMessageVO> delayedQueue =redisService.getDelayedQueue(queue);
        delayedQueue.offer(build,3,TimeUnit.SECONDS);
    }

    @Override
    public Boolean subtractionSkuStock(SubtractionSkuStockMessageVO subtractionSkuStockMessageVO) {
        Long sku = subtractionSkuStockMessageVO.getSku();
        Integer row = activitySkuDao.subtractionSkuStock(sku);
        if (row == 0){
            log.info("库存更新失败");
            return false;
        }
        return true;

    }

    @Override
    public SubtractionSkuStockMessageVO takeQueueValue(String activitySkuCountQueryKey) {
        RBlockingQueue queue = redisService.getBlockingQueue(activitySkuCountQueryKey);
        SubtractionSkuStockMessageVO subtractionSkuStockMessageVO = (SubtractionSkuStockMessageVO) queue.poll();
        return subtractionSkuStockMessageVO;
    }

    @Override
    public void clearActivitySkuStock(Long sku) {
        activitySkuDao.clearActivitySkuStock(sku);
    }

    /**
     * 清空redis延迟队列中的消息
     *
     */
    @Override
    public void clearQueueValue(Long sku) {
        String cacheKey = Constants.RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY +sku;
        RBlockingQueue<SubtractionSkuStockMessageVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
        destinationQueue.clear();
    }

    @Override
    public ActivityAccountDayEntity queryActivityAccountDayEntityByUserIdAndActivityId(String userId, Long activityId,String day) {

        RaffleActivityAccountDay raffleActivityAccountDayReq = RaffleActivityAccountDay.builder()
                .userId(userId)
                .activityId(activityId)
                .day(day)
                .build();

        RaffleActivityAccountDay raffleActivityAccountDay = activityAccountDayDao.queryActivityAccountDayByUserId(raffleActivityAccountDayReq);

        if (raffleActivityAccountDay != null) {
            ActivityAccountDayEntity activityAccountDayEntity = ActivityAccountDayEntity.builder()
                    .userId(raffleActivityAccountDay.getUserId())
                    .activityId(raffleActivityAccountDay.getActivityId())
                    .day(raffleActivityAccountDay.getDay())
                    .dayCount(raffleActivityAccountDay.getDayCount())
                    .dayCountSurplus(raffleActivityAccountDay.getDayCountSurplus())
                    .build();
            return activityAccountDayEntity;
        } else return null;

    }

    @Override
    public ActivityAccountMonthEntity queryActivityAccountMonthEntityByUserId(String userId,Long activityId,String month) {
        RaffleActivityAccountMonth raffleActivityAccountMonthReq = RaffleActivityAccountMonth.builder()
                .month(month)
                .activityId(activityId)
                .userId(userId)
                .build();

        RaffleActivityAccountMonth raffleActivityAccountMonth = activityAccountMonthDao.queryActivityAccountMonthByUserId(raffleActivityAccountMonthReq);
        if (raffleActivityAccountMonth != null) {
            ActivityAccountMonthEntity activityAccountMonthEntity = ActivityAccountMonthEntity.builder()
                    .userId(raffleActivityAccountMonth.getUserId())
                    .activityId(raffleActivityAccountMonth.getActivityId())
                    .month(raffleActivityAccountMonth.getMonth())
                    .monthCount(raffleActivityAccountMonth.getMonthCount())
                    .monthCountSurplus(raffleActivityAccountMonth.getMonthCountSurplus())
                    .build();
            return activityAccountMonthEntity;
        }else return null;

    }



    /**
     * 保存用户抽奖订单
     * 额度扣除
     * @param userRaffleOrderEntity 抽奖订单
     * @param currentDate 当前时间
     */
    @Override
    public void saveUserRaffleOrderEntity(UserRaffleOrderEntity userRaffleOrderEntity,Date currentDate) {

        //1.获取相关数据
        Long activityId = userRaffleOrderEntity.getActivityId();
        String userId = userRaffleOrderEntity.getUserId();

        //2.将各个账户数据更改后存入数据库
        // 统一切换路由，以下事务内的所有操作，都走一个路由
        try {
            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                        //更新总额度
                        RaffleActivityAccount raffleActivityAccount =RaffleActivityAccount.builder()
                            .activityId(activityId)
                            .userId(userId)
                            .build();

                        int rowAccount = activityAccountDao.updateActivityAccountSubtractionQuota(raffleActivityAccount);

                        if (rowAccount != 1){
                            status.setRollbackOnly();
                            log.info("数据库总账户数更新不为1,异常");
                            throw new AppException(ResponseCode.ACCOUNT_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_QUOTA_ERROR.getInfo());
                        }

                        //更新当前日额度
                        RaffleActivityAccountDay raffleActivityAccountDay = RaffleActivityAccountDay.builder()
                                .activityId(activityId)
                                .userId(userId)
                                .day(Constants.dateFormatDay.format(currentDate))
                                .build();
                        int rowAccountDay = activityAccountDayDao.updateActivityAccountDaySubtractionQuota(raffleActivityAccountDay);
                        if (rowAccountDay != 1){
                            status.setRollbackOnly();
                            log.info("数据库天账户数更新不为1,异常");
                            throw new AppException(ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getInfo());
                        }

                        //更新日额度
                        RaffleActivityAccountMonth raffleActivityAccountMonth = RaffleActivityAccountMonth.builder()
                                .activityId(activityId)
                                .userId(userId)
                                .month(Constants.dateFormatMonth.format(currentDate))
                                .build();

                        int rowAccountMonth = activityAccountMonthDao.updateActivityAccountMonthSubtractionQuota(raffleActivityAccountMonth);

                        if (rowAccountMonth != 1){
                            status.setRollbackOnly();
                            log.info("数据库月账户数更新不为1,异常");
                            throw new AppException(ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getInfo());
                        }

                    UserRaffleOrder userRaffleOrder = UserRaffleOrder.builder()
                            .userId(userRaffleOrderEntity.getUserId())
                            .activityId(userRaffleOrderEntity.getActivityId())
                            .activityName(userRaffleOrderEntity.getActivityName())
                            .strategyId(userRaffleOrderEntity.getStrategyId())
                            .orderId(userRaffleOrderEntity.getOrderId())
                            .orderTime(userRaffleOrderEntity.getOrderTime())
                            .orderState(userRaffleOrderEntity.getOrderState().getCode())
                            .build();

                    userRaffleOrderDao.insert(userRaffleOrder);
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入创建参与活动记录，唯一索引冲突 userId: {} activityId: {}", userId, activityId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }


            });
        } finally {
            dbRouter.clear();
        }


    }

    /**
     * 查询活动日额度账户月额度账户是否存在,不存在则从总账户同步
     * 涉及查询和修改两个操作,用分布式锁保证原子性
     * @param userId
     * @param activityId
     * @param currentDate
     */
    @Override
    public void checkMonthDayIsExist(String userId, Long activityId, Date currentDate) {

        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK + userId + Constants.UNDERLINE + activityId);


        try {
            RaffleActivityAccount activityAccount = activityAccountDao.queryRaffleActivityAccountByUserId(
                    RaffleActivityAccount.builder()
                            .activityId(activityId)
                            .userId(userId)
                            .build());

            if (activityAccount == null || activityAccount.getTotalCountSurplus() < 0) {
                log.info("活动库存不足");
                throw new AppException(ResponseCode.ACCOUNT_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_QUOTA_ERROR.getInfo());
            }
            String day = Constants.dateFormatDay.format(currentDate);
            String month =Constants.dateFormatMonth.format(currentDate);
            //检查日额度记录
            RaffleActivityAccountDay activityAccountDay = activityAccountDayDao.queryActivityAccountDayByUserId(RaffleActivityAccountDay.builder()
                    .activityId(activityId)
                    .userId(userId)
                    .build());

            if (activityAccountDay != null && activityAccountDay.getDayCountSurplus() < 0) {
                log.info("日活动库存不足");
                throw new AppException(ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_DAY_QUOTA_ERROR.getInfo());
            } else if (activityAccountDay == null) {
                activityAccountDayDao.insertActivityAccountDay(RaffleActivityAccountDay.builder()
                        .userId(activityAccount.getUserId())
                        .activityId(activityAccount.getActivityId())
                        .day(day)
                        .dayCount(activityAccount.getDayCount())
                        .dayCountSurplus(activityAccount.getDayCountSurplus())
                        .createTime(new Date())
                        .build());
            }


            //检查月额度记录
            RaffleActivityAccountMonth activityAccountMonth = activityAccountMonthDao.queryActivityAccountMonthByUserId(RaffleActivityAccountMonth.builder()
                    .month(month)
                    .activityId(activityId)
                    .userId(userId)
                    .build());
            if (activityAccountMonth != null && activityAccountMonth.getMonthCountSurplus() < 0) {
                log.info("月活动库存不足");
                throw new AppException(ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getCode(), ResponseCode.ACCOUNT_MONTH_QUOTA_ERROR.getInfo());
            } else if (activityAccountMonth == null) {

                activityAccountMonthDao.insertActivityAccountMonth(RaffleActivityAccountMonth.builder()
                        .userId(activityAccount.getUserId())
                        .activityId(activityAccount.getActivityId())
                        .month(month)
                        .monthCount(activityAccount.getMonthCount())
                        .monthCountSurplus(activityAccount.getMonthCountSurplus())
                        .createTime(new Date())
                        .build());
            }
        }finally {
            lock.unlock();
        }
    }

    /**
     * 保存sku订单
     * 不直接更新额度,只插入订单
     * @param createOrderAggregate
     */
    @Override
    public void doSaveCreditPayOrder(CreateOrderAggregate createOrderAggregate) {
        try {
            // 创建交易订单
            ActivityOrderEntity activityOrderEntity = createOrderAggregate.getActivityOrderEntity();
            RaffleActivityOrder raffleActivityOrder = new RaffleActivityOrder();
            raffleActivityOrder.setUserId(activityOrderEntity.getUserId());
            raffleActivityOrder.setSku(activityOrderEntity.getSku());
            raffleActivityOrder.setActivityId(activityOrderEntity.getActivityId());
            raffleActivityOrder.setActivityName(activityOrderEntity.getActivityName());
            raffleActivityOrder.setStrategyId(activityOrderEntity.getStrategyId());
            raffleActivityOrder.setOrderId(activityOrderEntity.getOrderId());
            raffleActivityOrder.setOrderTime(activityOrderEntity.getOrderTime());
            raffleActivityOrder.setTotalCount(activityOrderEntity.getTotalCount());
            raffleActivityOrder.setDayCount(activityOrderEntity.getDayCount());
            raffleActivityOrder.setMonthCount(activityOrderEntity.getMonthCount());
            raffleActivityOrder.setTotalCount(createOrderAggregate.getTotalCount());
            raffleActivityOrder.setDayCount(createOrderAggregate.getDayCount());
            raffleActivityOrder.setMonthCount(createOrderAggregate.getMonthCount());
            raffleActivityOrder.setPayAmount(activityOrderEntity.getPayAmount());
            raffleActivityOrder.setState(activityOrderEntity.getState().getCode());
            raffleActivityOrder.setOutBusinessNo(activityOrderEntity.getOutBusinessNo());

            // 以用户ID作为切分键，通过 doRouter 设定路由【这样就保证了下面的操作，都是同一个链接下，也就保证了事务的特性】
            dbRouter.doRouter(createOrderAggregate.getUserId());

            // 编程式事务
            transactionTemplate.execute(status -> {
                try {
                    activityOrderDao.insert(raffleActivityOrder);
                    return 1;
                } catch (DuplicateKeyException e) {
                    status.setRollbackOnly();
                    log.error("写入订单记录，唯一索引冲突 userId: {} activityId: {} sku: {}", activityOrderEntity.getUserId(), activityOrderEntity.getActivityId(), activityOrderEntity.getSku(), e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            dbRouter.clear();
        }
    }

    /**
     * 保存sku订单
     * 直接更新额度
     * @param createOrderAggregate 聚合对象:sku订单和额度信息
     */
    @Override
    public void doSaveNoPayOrder(CreateOrderAggregate createOrderAggregate) {

        //1.构建订单数据
        ActivityOrderEntity activityOrderEntity =createOrderAggregate.getActivityOrderEntity();
        RaffleActivityOrder raffleActivityOrder = RaffleActivityOrder.builder()
                .userId(activityOrderEntity.getUserId())
                .sku(activityOrderEntity.getSku())
                .activityId(activityOrderEntity.getActivityId())
                .activityName(activityOrderEntity.getActivityName())
                .strategyId(activityOrderEntity.getStrategyId())
                .orderId(activityOrderEntity.getOrderId())
                .orderTime(activityOrderEntity.getOrderTime())
                .state(activityOrderEntity.getState().getCode())
                .totalCount(activityOrderEntity.getTotalCount())
                .dayCount(activityOrderEntity.getDayCount())
                .monthCount(activityOrderEntity.getMonthCount())
                .outBusinessNo(activityOrderEntity.getOutBusinessNo())//用来防止同一次行为多次下单,数据库中这个字段是唯一索引
                .build();


        //2.构建账户数据
        RaffleActivityAccount raffleActivityAccount = RaffleActivityAccount.builder()
                .userId(createOrderAggregate.getUserId())
                .activityId(createOrderAggregate.getActivityId())
                .totalCount(createOrderAggregate.getTotalCount())
                .totalCountSurplus(createOrderAggregate.getTotalCount())
                .dayCount(createOrderAggregate.getDayCount())
                .dayCountSurplus(createOrderAggregate.getDayCount())
                .monthCount(createOrderAggregate.getMonthCount())
                .monthCountSurplus(createOrderAggregate.getMonthCount())
                .build();

        //构建月账户数据
        Date date = new Date();
        String month = dateFormatMonth.format(date);
        String day = dateFormatDay.format(date);

        RaffleActivityAccountMonth raffleActivityAccountMonth = RaffleActivityAccountMonth.builder()
                .activityId(createOrderAggregate.getActivityId())
                .userId(createOrderAggregate.getUserId())
                .monthCountSurplus(createOrderAggregate.getMonthCount())
                .monthCount(createOrderAggregate.getMonthCount())
                .month(month)
                .build();

        RaffleActivityAccountDay raffleActivityAccountDay = RaffleActivityAccountDay.builder()
                .activityId(createOrderAggregate.getActivityId())
                .userId(createOrderAggregate.getUserId())
                .day(day)
                .dayCount(createOrderAggregate.getDayCount())
                .dayCountSurplus(createOrderAggregate.getDayCount())
                .build();

        dbRouter.doRouter(createOrderAggregate.getUserId()); //在这里进行路由,而不是注解路由,这样可以保证同一次连接
        //编程式事务,用来自由灵活处理回滚,不单单只是出错时
        transactionTemplate.execute(status -> {
//            try{
            //3.将两种数据用事务添加进数据库
            activityOrderDao.insert(raffleActivityOrder);
            int count = activityAccountDao.updateAccountQuota(raffleActivityAccount);
            int countDay = activityAccountDayDao.updateActivityAccountDayAddQuota(raffleActivityAccountDay);
            int countMonth = activityAccountMonthDao.updateActivityAccountMonthAddQuota(raffleActivityAccountMonth);
            if (count != 0 || countDay != 0 || countMonth != 0) {
                throw new AppException("额度更新出错");
            }

            return 1;
//            }catch (DuplicateKeyException e){
//                status.setRollbackOnly();
//                log.info("唯一索引冲突");
//                throw new AppException(ResponseCode.INDEX_DUP.getCode());
//            }finally {
//                dbRouter.clear();
//            }

        });

    }

    /**
     * sku商品积分更新订单请求
     * 更新sku商品积分订单状态
     * sku商品入账 : 更新用户额度
     * 细节更新订单状态时加上where state = 'wait_pay',搭配更新行数可以做到消息幂等性
     * @param deliveryOrderEntity
     */
    @Override
    public void updateOrder(DeliveryOrderEntity deliveryOrderEntity) {
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_UPDATE_LOCK + deliveryOrderEntity.getUserId());
        try {
            lock.lock(3, TimeUnit.SECONDS);

            // 1.查询订单
            RaffleActivityOrder raffleActivityOrderReq = new RaffleActivityOrder();
            raffleActivityOrderReq.setUserId(deliveryOrderEntity.getUserId());
            raffleActivityOrderReq.setOutBusinessNo(deliveryOrderEntity.getOutBusinessNo());
            RaffleActivityOrder raffleActivityOrderRes = activityOrderDao.queryActivityOrder(raffleActivityOrderReq);

            //2.构建账户数据
            RaffleActivityAccount raffleActivityAccount = RaffleActivityAccount.builder()
                    .userId(raffleActivityOrderRes.getUserId())
                    .activityId(raffleActivityOrderRes.getActivityId())
                    .totalCount(raffleActivityOrderRes.getTotalCount())
                    .totalCountSurplus(raffleActivityOrderRes.getTotalCount())
                    .dayCount(raffleActivityOrderRes.getDayCount())
                    .dayCountSurplus(raffleActivityOrderRes.getDayCount())
                    .monthCount(raffleActivityOrderRes.getMonthCount())
                    .monthCountSurplus(raffleActivityOrderRes.getMonthCount())
                    .build();

            //构建月账户数据
            Date date = new Date();
            String month = dateFormatMonth.format(date);
            String day = dateFormatDay.format(date);

            RaffleActivityAccountMonth raffleActivityAccountMonth = RaffleActivityAccountMonth.builder()
                    .activityId(raffleActivityOrderRes.getActivityId())
                    .userId(raffleActivityOrderRes.getUserId())
                    .monthCountSurplus(raffleActivityOrderRes.getMonthCount())
                    .monthCount(raffleActivityOrderRes.getMonthCount())
                    .month(month)
                    .build();

            RaffleActivityAccountDay raffleActivityAccountDay = RaffleActivityAccountDay.builder()
                    .activityId(raffleActivityOrderRes.getActivityId())
                    .userId(raffleActivityOrderRes.getUserId())
                    .day(day)
                    .dayCount(raffleActivityOrderRes.getDayCount())
                    .dayCountSurplus(raffleActivityOrderRes.getDayCount())
                    .build();

            dbRouter.doRouter(raffleActivityOrderRes.getUserId()); //在这里进行路由,而不是注解路由,这样可以保证同一次连接
            //编程式事务,用来自由灵活处理回滚,不单单只是出错时
            transactionTemplate.execute(status -> {

                /**
                 * 这里的更新行和sql语句做配合,来保证消息幂等性和防止消息重复,
                 * sql是 update raffle_activity_order set state = 'completed', update_time = now()
                 * where user_id = #{userId} and out_business_no = #{outBusinessNo} and state = 'wait_pay'
                 * 其中and state = 'wait_pay'保证了即使多条消息数据库只更新一次,搭配更新字段回滚
                 */
                int updateCount = activityOrderDao.updateOrderCompleted(raffleActivityOrderRes);
                if (1 != updateCount) {
                    status.setRollbackOnly();
                    return 1;
                }
                int count = activityAccountDao.updateAccountQuota(raffleActivityAccount);
                int countDay = activityAccountDayDao.updateActivityAccountDayAddQuota(raffleActivityAccountDay);
                int countMonth = activityAccountMonthDao.updateActivityAccountMonthAddQuota(raffleActivityAccountMonth);
                if (count != 0 || countDay != 0 || countMonth != 0) {
                    throw new AppException("额度更新出错");
                }
                return 1;
            });
        }finally {
            lock.unlock();
            dbRouter.clear();
        }

    }

    @Override
    public UserRaffleOrderEntity  queryNoUsedRaffleOrder(PartakeRaffleActivityEntity partakeRaffleActivityEntity) {
        // 查询数据
        UserRaffleOrder userRaffleOrderReq = new UserRaffleOrder();
        userRaffleOrderReq.setUserId(partakeRaffleActivityEntity.getUserId());
        userRaffleOrderReq.setActivityId(partakeRaffleActivityEntity.getActivityId());
        UserRaffleOrder userRaffleOrderRes = userRaffleOrderDao.queryNoUsedRaffleOrder(userRaffleOrderReq);
        if (null == userRaffleOrderRes) return null;
        // 封装结果
        UserRaffleOrderEntity userRaffleOrderEntity = new UserRaffleOrderEntity();
        userRaffleOrderEntity.setUserId(userRaffleOrderRes.getUserId());
        userRaffleOrderEntity.setActivityId(userRaffleOrderRes.getActivityId());
        userRaffleOrderEntity.setActivityName(userRaffleOrderRes.getActivityName());
        userRaffleOrderEntity.setStrategyId(userRaffleOrderRes.getStrategyId());
        userRaffleOrderEntity.setOrderId(userRaffleOrderRes.getOrderId());
        userRaffleOrderEntity.setOrderTime(userRaffleOrderRes.getOrderTime());
        userRaffleOrderEntity.setOrderState(UserRaffleOrderStateVO.valueOf(userRaffleOrderRes.getOrderState()));
        return userRaffleOrderEntity;
    }

    /**
     *  根据活动id查询活动sku实体
     *  一个活动可能有多个活动sku
     * @param activityId 活动id
     * @return 活动实体
     */
    @Override
    public List<ActivitySkuEntity> queryActivitySkuEntitiesByActivityId(Long activityId) {
        List<RaffleActivitySku> raffleActivitySkus = activitySkuDao.queryActivitySkuEntitiesByActivityId(activityId);
        List<ActivitySkuEntity> activitySkuEntities = new ArrayList<>();
        for (RaffleActivitySku raffleActivitySku : raffleActivitySkus){
            ActivitySkuEntity activitySkuEntity = ActivitySkuEntity.builder()
                        .sku(raffleActivitySku.getSku())
                        .activityId(raffleActivitySku.getActivityId())
                        .activityCountId(raffleActivitySku.getActivityCountId())
                        .stockCount(raffleActivitySku.getStockCount())
                        .stockCountSurplus(raffleActivitySku.getStockCountSurplus())
                        .build();
            activitySkuEntities.add(activitySkuEntity);
        }
        return activitySkuEntities;
    }

    /**
     * 查询某用户某天的日额度数据
     * @param activityId
     * @param userId
     * @param day
     * @return
     */
    @Override
    public ActivityAccountDayEntity queryRaffleActivityAccountDay(Long activityId, String userId, String day) {

        RaffleActivityAccountDay raffleActivityAccountDay = activityAccountDayDao.queryActivityAccountDayByUserId(RaffleActivityAccountDay.builder()
                .userId(userId)
                .activityId(activityId)
                .day(day)
                .build());

        ActivityAccountDayEntity activityAccountDayEntity = ActivityAccountDayEntity.builder()
                .userId(raffleActivityAccountDay.getUserId())
                .activityId(raffleActivityAccountDay.getActivityId())
                .day(raffleActivityAccountDay.getDay())
                .dayCount(raffleActivityAccountDay.getDayCount())
                .dayCountSurplus(raffleActivityAccountDay.getDayCountSurplus())
                .build();

        return activityAccountDayEntity;
    }

    /**
     * 查询用户额度次数
     * @param activityId
     * @param userId
     * @return
     */
    @Override
    public ActivityAccountEntity queryUserActivityAccount(Long activityId, String userId) {
        Date date = new Date();

        RaffleActivityAccount raffleActivityAccount = activityAccountDao.queryRaffleActivityAccountByUserId(RaffleActivityAccount.builder()
                .userId(userId)
                .activityId(activityId)
                .build());

        if(raffleActivityAccount ==null)
            return null;
        RaffleActivityAccountDay raffleActivityAccountDay = activityAccountDayDao.queryActivityAccountDayByUserId(RaffleActivityAccountDay.builder()
                .userId(userId)
                .activityId(activityId)
                .day(dateFormatDay.format(date))
                .build());
        RaffleActivityAccountMonth raffleActivityAccountMonth = activityAccountMonthDao.queryActivityAccountMonthByUserId(RaffleActivityAccountMonth.builder()
                .userId(userId)
                .activityId(activityId)
                .month(dateFormatMonth.format(date))
                .build());


        ActivityAccountEntity activityAccountEntity = new ActivityAccountEntity();
        activityAccountEntity.setActivityId(activityId);
        activityAccountEntity.setUserId(userId);
        activityAccountEntity.setTotalCount(raffleActivityAccount.getTotalCount());
        activityAccountEntity.setTotalCountSurplus(raffleActivityAccount.getTotalCountSurplus());

        if (raffleActivityAccountDay == null){
            activityAccountEntity.setDayCount(raffleActivityAccount.getDayCount());
            activityAccountEntity.setDayCountSurplus(raffleActivityAccount.getDayCountSurplus());
        }else {
            activityAccountEntity.setDayCount(raffleActivityAccountDay.getDayCount());
            activityAccountEntity.setDayCountSurplus(raffleActivityAccountDay.getDayCountSurplus());
        }


        if (raffleActivityAccountMonth == null){
            activityAccountEntity.setMonthCount(raffleActivityAccount.getMonthCount());
            activityAccountEntity.setMonthCountSurplus(raffleActivityAccount.getMonthCountSurplus());
        }else{
            activityAccountEntity.setMonthCount(raffleActivityAccountMonth.getMonthCount());
            activityAccountEntity.setMonthCountSurplus(raffleActivityAccountMonth.getMonthCountSurplus());
        }


        return activityAccountEntity;
    }



    @Override
    public Integer queryActivityAccountTotalUseCountByActivityId(String userId, Long activityId) {
        RaffleActivityAccount raffleActivityAccount = activityAccountDao.queryRaffleActivityAccountByUserId(RaffleActivityAccount.builder()
                .activityId(activityId)
                .userId(userId)
                .build());

        return raffleActivityAccount.getTotalCount() - raffleActivityAccount.getTotalCountSurplus();
    }

    /**
     * 插入日额度记录
     * @param activityAccountDayEntity
     */
    @Override
    public void saveActivityAccountDay(ActivityAccountDayEntity activityAccountDayEntity) {
        RaffleActivityAccountDay ActivityAccountDay = RaffleActivityAccountDay.builder()
                .userId(activityAccountDayEntity.getUserId())
                .activityId(activityAccountDayEntity.getActivityId())
                .day(activityAccountDayEntity.getDay())
                .dayCount(activityAccountDayEntity.getDayCount())
                .dayCountSurplus(activityAccountDayEntity.getDayCountSurplus())
                .createTime(new Date())
                .build();
        activityAccountDayDao.insertActivityAccountDay(ActivityAccountDay);
    }

    /**
     * 插入月额度记录
     * @param activityAccountMonthEntity
     */
    @Override
    public void saveActivityAccountMonth(ActivityAccountMonthEntity activityAccountMonthEntity) {
        RaffleActivityAccountMonth ActivityAccountMonth = RaffleActivityAccountMonth.builder()
                .userId(activityAccountMonthEntity.getUserId())
                .activityId(activityAccountMonthEntity.getActivityId())
                .month(activityAccountMonthEntity.getMonth())
                .monthCount(activityAccountMonthEntity.getMonthCount())
                .monthCountSurplus(activityAccountMonthEntity.getMonthCountSurplus())
                .createTime(new Date())
                .build();
        activityAccountMonthDao.insertActivityAccountMonth(ActivityAccountMonth);
    }


}
